#include "common/pch.h"

#include <string.h>

#include <array>
#include <algorithm>

#include <cftc/reflect/class.h>
#include <cftc/reflect/object.h>
#include <cftc/reflect/type.h>
#include <cftc/reflect/field.h>
#include <cftc/reflect/annotation.h>

#include <cftc/magicenum/magicenum.h>
#include <cftc/util/strutils.h>

#include "cs/msg_descriptor.h"

#include "opcode/opcode.h"

namespace cs
{

	unsigned short MsgDescriptor::FindMsgIdWithMetaClass(const cftc::reflect::Class* meta_class)
	{
		{
			auto fields = cftc::magicenum::GetEnumFields<Opcode1_e>();
			for (const std::pair<std::string, int>& pair : *fields) {
				std::string name = pair.first;
				cftc::util::ReplaceString(name, "CMSG_", "CM");
				cftc::util::ReplaceString(name, "SMSG_", "SM");
				cftc::util::ReplaceString(name, "_", "");
				name = cftc::util::ToLower(name);
				if (name == cftc::util::ToLower(std::string(meta_class->GetClassName()))) {
					return pair.second;
				}
			}
		}
		{
			auto fields = cftc::magicenum::GetEnumFields<Opcode2_e>();
			for (const std::pair<std::string, int>& pair : *fields) {
				std::string name =pair.first;
				cftc::util::ReplaceString(name, "CMSG_", "CM");
				cftc::util::ReplaceString(name, "SMSG_", "SM");
				cftc::util::ReplaceString(name, "_", "");
				name = cftc::util::ToLower(name);
				if (name == cftc::util::ToLower(std::string(meta_class->GetClassName()))) {
					return pair.second;
				}
			}
		}
		return 0;
	}

	bool MsgDescriptor::DecodeMsg(const char* buf, unsigned int buflen, unsigned int& offset, cftc::reflect::Object* obj)
	{
		std::array<const cftc::reflect::Class*, 50> meta_classes;
		std::size_t num = 0;
		{
			auto meta_class = obj->GetClass();
			while (meta_class) {
				meta_classes[num] = meta_class;
				++num;
				meta_class = meta_class->GetParent();
			}
		}
		if (num <= 0) {
			abort();
		}
		bool ok = true;
		for (std::size_t i = 0; i < num; ++i) {
			auto meta_class = meta_classes[num - i - 1];
			meta_class->TraverseFields(
				[buf, buflen, obj, &offset, &ok](const std::shared_ptr<cftc::reflect::Field>& field) mutable -> bool
				{
					ok = DecodeField(buf, buflen, offset, (char*)obj + field->GetOffset(), field->GetType(), field);
					return ok;
				});
		}
		return ok;
	}

	bool MsgDescriptor::DecodeField(const char* buf, unsigned int buflen, unsigned int& offset,
		void* data, const std::shared_ptr<cftc::reflect::Type>& type, const std::shared_ptr<cftc::reflect::Field>& field)
	{
#pragma warning(push)
#pragma warning(error: 4062)
		if (type->IsPointer()) {
			type->TryInPlaceNew(data);
		}
		void* rawdata_ptr = type->GetRawDataPtr(data);
		const cftc::ducktype::XValue* fix_string_len = nullptr;
		const cftc::ducktype::XValue* list_size_type = nullptr;
		if (field) {
			auto wow_anno = field->GetAnnotation("wow");
			if (wow_anno) {
				fix_string_len = wow_anno->GetOption("fix_string_len");
				list_size_type = wow_anno->GetOption("list_size_type");
			}
		}
		switch (type->GetKind())
		{
		case A8ReflectTypeKind_e::kNone:
		{
			abort();
			break;
		}
		case A8ReflectTypeKind_e::kInt8:
		{
			if (offset + sizeof(char) > buflen) {
				return false;
			}
			*(char*)rawdata_ptr = *((char*)(buf + offset));
			offset += sizeof(char);
			break;
		}
		case A8ReflectTypeKind_e::kUInt8:
		{
			if (offset + sizeof(unsigned char) > buflen) {
				return false;
			}
			*(unsigned char*)rawdata_ptr = *((unsigned char*)(buf + offset));
			offset += sizeof(unsigned char);
			break;
		}
		case A8ReflectTypeKind_e::kInt16:
		{
			if (offset + sizeof(short) > buflen) {
				return false;
			}
			*(short*)rawdata_ptr = *((short*)(buf + offset));
			offset += sizeof(short);
			break;
		}
		case A8ReflectTypeKind_e::kUInt16:
		{
			if (offset + sizeof(unsigned short) > buflen) {
				return false;
			}
			*(unsigned short*)rawdata_ptr = *((unsigned short*)(buf + offset));
			offset += sizeof(unsigned short);
			break;
		}
		case A8ReflectTypeKind_e::kInt32:
		{
			if (offset + sizeof(int) > buflen) {
				return false;
			}
			*(int*)rawdata_ptr = *((int*)(buf + offset));
			offset += sizeof(int);
			break;
		}
		case A8ReflectTypeKind_e::kUInt32:
		{
			if (offset + sizeof(unsigned int) > buflen) {
				return false;
			}
			*(unsigned int*)rawdata_ptr = *((unsigned int*)(buf + offset));
			offset += sizeof(unsigned int);
			break;
		}
		case A8ReflectTypeKind_e::kInt64:
		{
			if (offset + sizeof(long long) > buflen) {
				return false;
			}
			*(long long*)rawdata_ptr = *((long long*)(buf + offset));
			offset += sizeof(long long);
			break;
		}
		case A8ReflectTypeKind_e::kUInt64:
		{
			if (offset + sizeof(unsigned long long) > buflen) {
				return false;
			}
			*(unsigned long long*)rawdata_ptr = *((unsigned long long*)(buf + offset));
			offset += sizeof(unsigned long long);
			break;
		}
		case A8ReflectTypeKind_e::kFloat:
		{
			if (offset + sizeof(float) > buflen) {
				return false;
			}
			*(float*)rawdata_ptr = *((float*)(buf + offset));
			offset += sizeof(float);
			break;
		}
		case A8ReflectTypeKind_e::kDouble:
		{
			if (offset + sizeof(double) > buflen) {
				return false;
			}
			*(double*)rawdata_ptr = *((double*)(buf + offset));
			offset += sizeof(double);
			break;
		}
		case A8ReflectTypeKind_e::kString:
		{
			if (fix_string_len) {
				if (offset + fix_string_len->Get<int>() > buflen) {
					return false;
				}
				*(std::string*)rawdata_ptr = std::string(buf + offset, fix_string_len->Get<int>());
				offset += fix_string_len->Get<int>();
			} else {
				const char* end = std::find(buf + offset, buf + buflen, '\0');
				*(std::string*)rawdata_ptr = std::string(buf + offset, end - (buf + offset));
				offset += (unsigned int)(end - (buf + offset) + 1);
			}
			break;
		}
		case A8ReflectTypeKind_e::kObject:
		{
			return DecodeMsg(buf, buflen, offset, (cftc::reflect::Object*)rawdata_ptr);
			break;
		}
		case A8ReflectTypeKind_e::kXObject:
		{
			abort();
			break;
		}
		case A8ReflectTypeKind_e::kVector:
		case A8ReflectTypeKind_e::kList:
		{
			int byte_size = 4;
			if (list_size_type) {
				if (list_size_type->Get<std::string>() == "int8" ||
					list_size_type->Get<std::string>() == "uint8") {
					byte_size = 1;
				} else if (list_size_type->Get<std::string>() == "int16" ||
					list_size_type->Get<std::string>() == "uint16") {
					byte_size = 2;
				} else if (list_size_type->Get<std::string>() == "int32" ||
					list_size_type->Get<std::string>() == "uint32") {
					byte_size = 4;
				} else {
					abort();
				}
			}
			if (offset + byte_size > buflen) {
				return false;
			}
			long long size = 0;
			memcpy((char*) & size + (8 - byte_size), buf + offset, byte_size);
			offset += byte_size;
			for (long long i = 0; i < size; ++i) {
				void* element = type->NewElement(rawdata_ptr, nullptr);
				if (!DecodeField(buf, buflen, offset, element, type->GetElementType(), field)) {
					return false;
				}
			}
			abort();
			break;
		}
		case A8ReflectTypeKind_e::kMap:
		{
			abort();
			break;
		}
		SWITCH_ENUM_CHECK()
		}
#pragma warning(pop)
		return true;
	}

	unsigned int MsgDescriptor::CalcSerializeFieldByteSize(const void* data, const std::shared_ptr<cftc::reflect::Type>& type, const std::shared_ptr<cftc::reflect::Field>& field)
	{
#pragma warning(push)
#pragma warning(error: 4062)
		unsigned int byte_size = 0;
		void* rawdata_ptr = type->GetRawDataPtr(data);
		const cftc::ducktype::XValue* fix_string_len = nullptr;
		const cftc::ducktype::XValue* list_size_type = nullptr;
		if (field) {
			auto wow_anno = field->GetAnnotation("wow");
			if (wow_anno) {
				fix_string_len = wow_anno->GetOption("fix_string_len");
				list_size_type = wow_anno->GetOption("list_size_type");
			}
		}
		switch (type->GetKind())
		{
		case A8ReflectTypeKind_e::kNone:
		{
			abort();
			break;
		}
		case A8ReflectTypeKind_e::kInt8:
		{
			byte_size += sizeof(char);
			break;
		}
		case A8ReflectTypeKind_e::kUInt8:
		{
			byte_size += sizeof(unsigned char);
			break;
		}
		case A8ReflectTypeKind_e::kInt16:
		{
			byte_size += sizeof(short);
			break;
		}
		case A8ReflectTypeKind_e::kUInt16:
		{
			byte_size += sizeof(unsigned short);
			break;
		}
		case A8ReflectTypeKind_e::kInt32:
		{
			byte_size += sizeof(int);
			break;
		}
		case A8ReflectTypeKind_e::kUInt32:
		{
			byte_size += sizeof(unsigned int);
			break;
		}
		case A8ReflectTypeKind_e::kInt64:
		{
			byte_size += sizeof(long long);
			break;
		}
		case A8ReflectTypeKind_e::kUInt64:
		{
			byte_size += sizeof(unsigned long long);
			break;
		}
		case A8ReflectTypeKind_e::kFloat:
		{
			byte_size += sizeof(float);
			break;
		}
		case A8ReflectTypeKind_e::kDouble:
		{
			byte_size += sizeof(double);
			break;
		}
		case A8ReflectTypeKind_e::kString:
		{
			if (fix_string_len) {
				byte_size += fix_string_len->Get<int>();
			}
			else {
				if (rawdata_ptr) {
					byte_size += (unsigned int)((std::string*)rawdata_ptr)->length() + 1;
				}
			}
			break;
		}
		case A8ReflectTypeKind_e::kObject:
		{
			return rawdata_ptr ? CalcSerializeMsgByteSize((cftc::reflect::Object*)rawdata_ptr) : 0;
			break;
		}
		case A8ReflectTypeKind_e::kXObject:
		{
			abort();
			break;
		}
		case A8ReflectTypeKind_e::kVector:
		case A8ReflectTypeKind_e::kList:
		{
			if (list_size_type) {
				if (list_size_type->Get<std::string>() == "carray") {
					byte_size += 0;
				} else if (list_size_type->Get<std::string>() == "uint8") {
					byte_size += 1;
				} else if (list_size_type->Get<std::string>() == "uint16") {
					byte_size += 2;
				} else if (list_size_type->Get<std::string>() == "uint32") {
					byte_size += 4;
				} else {
					abort();
				}
			} else {
				byte_size += 4;
			}
			type->TraverseElement(
				rawdata_ptr,
				[&byte_size, type, field] (const void* key, const void* val) mutable -> bool 
				{
					byte_size += CalcSerializeFieldByteSize(val, type->GetElementType(), field);
					return true;
				});
			break;
		}
		case A8ReflectTypeKind_e::kMap:
		{
			abort();
			break;
		}
		SWITCH_ENUM_CHECK()
		}
#pragma warning(pop)
		return byte_size;
	}

	unsigned int MsgDescriptor::CalcSerializeMsgByteSize(cftc::reflect::Object* msg)
	{
		unsigned int byte_size = 0;
		std::array<const cftc::reflect::Class*, 50> meta_classes;
		std::size_t num = 0;
		{
			auto meta_class = msg->GetClass();
			while (meta_class) {
				meta_classes[num] = meta_class;
				++num;
				meta_class = meta_class->GetParent();
			}
		}
		if (num <= 0) {
			abort();
		}
		bool ok = true;
		for (std::size_t i = 0; i < num; ++i) {
			auto meta_class = meta_classes[num - i - 1];
			meta_class->TraverseFields(
				[msg, &byte_size](const std::shared_ptr<cftc::reflect::Field>& field) mutable -> bool
				{
					byte_size += CalcSerializeFieldByteSize((char*)msg + field->GetOffset(), field->GetType(), field);
					return byte_size;
				});
		}
		return byte_size;
	}

	bool MsgDescriptor::SerializeMsg(cftc::reflect::Object* msg, unsigned char* buf, unsigned int buflen, unsigned int& offset)
	{
		std::array<const cftc::reflect::Class*, 50> meta_classes;
		std::size_t num = 0;
		{
			auto meta_class = msg->GetClass();
			while (meta_class) {
				meta_classes[num] = meta_class;
				++num;
				meta_class = meta_class->GetParent();
			}
		}
		if (num <= 0) {
			abort();
		}
		bool ok = true;
		for (std::size_t i = 0; i < num; ++i) {
			auto meta_class = meta_classes[num - i - 1];
			meta_class->TraverseFields(
				[msg, buf, buflen, &offset](const std::shared_ptr<cftc::reflect::Field>& field) mutable -> bool
				{
					return SerializeField((unsigned char*)msg + field->GetOffset(), field->GetType(), field, buf, buflen, offset);
				});
		}
		return true;
	}

	bool MsgDescriptor::SerializeField(void* data, const std::shared_ptr<cftc::reflect::Type>& type, const std::shared_ptr<cftc::reflect::Field>& field,
		unsigned char* buf, unsigned int buflen, unsigned int& offset)
	{
#pragma warning(push)
#pragma warning(error: 4062)
		void* rawdata_ptr = type->GetRawDataPtr(data);
		const cftc::ducktype::XValue* fix_string_len = nullptr;
		const cftc::ducktype::XValue* list_size_type = nullptr;
		if (field) {
			auto wow_anno = field->GetAnnotation("wow");
			if (wow_anno) {
				fix_string_len = wow_anno->GetOption("fix_string_len");
				list_size_type = wow_anno->GetOption("list_size_type");
			}
		}
		switch (type->GetKind())
		{
		case A8ReflectTypeKind_e::kNone:
		{
			abort();
			break;
		}
		case A8ReflectTypeKind_e::kInt8:
		{
			if (offset + sizeof(char) > buflen) {
				return false;
			}
			*((char*)(buf + offset)) = *(char*)rawdata_ptr;
			offset += sizeof(char);
			break;
		}
		case A8ReflectTypeKind_e::kUInt8:
		{
			if (offset + sizeof(unsigned char) > buflen) {
				return false;
			}
			*((unsigned char*)(buf + offset)) = *(unsigned char*)rawdata_ptr;
			offset += sizeof(unsigned char);
			break;
		}
		case A8ReflectTypeKind_e::kInt16:
		{
			if (offset + sizeof(short) > buflen) {
				return false;
			}
			*((short*)(buf + offset)) = *(short*)rawdata_ptr;
			offset += sizeof(short);
			break;
		}
		case A8ReflectTypeKind_e::kUInt16:
		{
			if (offset + sizeof(unsigned short) > buflen) {
				return false;
			}
			*((unsigned short*)(buf + offset)) = *(unsigned short*)rawdata_ptr;
			offset += sizeof(unsigned short);
			break;
		}
		case A8ReflectTypeKind_e::kInt32:
		{
			if (offset + sizeof(int) > buflen) {
				return false;
			}
			*((int*)(buf + offset)) = *(int*)rawdata_ptr;
			offset += sizeof(int);
			break;
		}
		case A8ReflectTypeKind_e::kUInt32:
		{
			if (offset + sizeof(unsigned int) > buflen) {
				return false;
			}
			*((unsigned int*)(buf + offset)) = *(unsigned int*)rawdata_ptr;
			offset += sizeof(unsigned int);
			break;
		}
		case A8ReflectTypeKind_e::kInt64:
		{
			if (offset + sizeof(long long) > buflen) {
				return false;
			}
			*((long long*)(buf + offset)) = *(long long*)rawdata_ptr;
			offset += sizeof(long long);
			break;
		}
		case A8ReflectTypeKind_e::kUInt64:
		{
			if (offset + sizeof(unsigned long long) > buflen) {
				return false;
			}
			*((unsigned long long*)(buf + offset)) = *(unsigned long long*)rawdata_ptr;
			offset += sizeof(unsigned long long);
			break;
		}
		case A8ReflectTypeKind_e::kFloat:
		{
			if (offset + sizeof(float) > buflen) {
				return false;
			}
			*((float*)(buf + offset)) = *(float*)rawdata_ptr;
			offset += sizeof(float);
			break;
		}
		case A8ReflectTypeKind_e::kDouble:
		{
			if (offset + sizeof(double) > buflen) {
				return false;
			}
			*((double*)(buf + offset)) = *(double*)rawdata_ptr;
			offset += sizeof(double);
			break;
		}
		case A8ReflectTypeKind_e::kString:
		{
			auto str = (std::string*)rawdata_ptr;
			if (fix_string_len) {
				if (offset + fix_string_len->Get<int>() > buflen) {
					abort();
					return false;
				}
				if (str->size() != fix_string_len->Get<int>()) {
					abort();
				}
				memcpy(buf + offset, str->data(), str->size());
				offset += fix_string_len->Get<int>();
			} else {
				if (offset + str->length() + 1 > buflen) {
					abort();
				}
				memcpy(buf + offset, str->data(), str->length());
				buf[offset + str->length()] = 0;
				offset += (unsigned int)(str->length() + 1);
			}
			break;
		}
		case A8ReflectTypeKind_e::kObject:
		{
			return SerializeMsg((cftc::reflect::Object*)rawdata_ptr, buf, buflen, offset);
			break;
		}
		case A8ReflectTypeKind_e::kXObject:
		{
			abort();
			break;
		}
		case A8ReflectTypeKind_e::kVector:
		case A8ReflectTypeKind_e::kList:
		{
			std::size_t size = type->GetElementsNum(rawdata_ptr);
			int byte_size = 4;
			if (list_size_type) {
				if (list_size_type->Get<std::string>() == "carray") {
					byte_size = 0;
				} else if (list_size_type->Get<std::string>() == "uint8") {
					byte_size = 1;
					if (offset + byte_size > buflen) {
						return false;
					}
					*((unsigned char*)(buf + offset)) = (unsigned char)size;
				} else if (list_size_type->Get<std::string>() == "uint16") {
					byte_size = 2;
					if (offset + byte_size > buflen) {
						return false;
					}
					*((unsigned short*)(buf + offset)) = (unsigned short)size;
				} else if (list_size_type->Get<std::string>() == "uint32") {
					byte_size = 4;
					if (offset + byte_size > buflen) {
						return false;
					}
					*((unsigned int*)(buf + offset)) = (unsigned int)size;
				} else {
					abort();
				}
			} else {
				if (offset + byte_size > buflen) {
					return false;
				}
				*((unsigned int*)(buf + offset)) = (unsigned int)size;
			}
			offset += byte_size;
			return type->TraverseElement(rawdata_ptr,
					[type, field, buf, buflen, &offset](const void* key, const void* val) mutable
					{
						if (!SerializeField((void*)val, type->GetElementType(), field, buf, buflen, offset)) {
							abort();
							return false;
						}
						return true;
					});
			break;
		}
		case A8ReflectTypeKind_e::kMap:
		{
			abort();
			break;
		}
		SWITCH_ENUM_CHECK()
		}
#pragma warning(pop)
		return true;
	}

}